﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using XDL.Framework;
using XDL.Framework.Particles;
using XDL.Framework.Weapons;
using JumpNFight.Weapons;
using XDL.Framework.Messaging;
using XDL.Framework.OpenSteer;

namespace JumpNFight.Simulation
{
    class World : 
        GameComponent,
        IEventDispatcherClient
    {
        public const int MaxPlayers = 2;
        Timer[] m_playerIdleTimers = new Timer[MaxPlayers];
        Player[] m_players = new Player[MaxPlayers];
        int m_nextBadGuyID = 1000;
        List<NPCBase> m_npcActors = new List<NPCBase>(16);
        List<CrateMessage> m_crateMessages = new List<CrateMessage>(4);
        Crate m_crate;
        static Random g_rnd = new Random();
        ParticleSystem m_smokePS;
        ParticleSystem m_teleportPS;
        List<IWeapon> m_weapons = new List<IWeapon>();
        int m_crateScore = 0;
        AudioManager m_audio;
        // some weapon system are handled differently (e.g. must be updated within the world Update loop)
        List<IWeaponSystem> m_weaponSystems = new List<IWeaponSystem>(8);
        // path finding entry point
        PathFinder m_pathFinder;
        //
        int m_currentPattern = 0;
        SpawnPattern[] m_patterns = new SpawnPattern[]
        {
            new SpawnPattern(TimeSpan.FromSeconds(1.5), TimeSpan.FromSeconds(1),"LRLRLR"),
            new SpawnPattern(TimeSpan.FromSeconds(1.75), TimeSpan.FromSeconds(0.75),"LLL"),
            new SpawnPattern(TimeSpan.FromSeconds(1.75), TimeSpan.FromSeconds(0.75),"RRR")
        };
        Timer m_shakeScreenTimer = new Timer(TimeSpan.FromSeconds(0.15), false);
        Vector2 m_shakingOffset = Vector2.Zero;
        Vector2 m_shakeForce = Vector2.Zero;

        public int CrateScore
        {
            get
            {
                return m_crateScore;
            }
        }

        public List<IWeapon> Weapons
        {
            get
            {
                return m_weapons;
            }
        }

        public List<NPCBase> NPCs
        {
            get
            {
                return m_npcActors;
            }
        }
        public const int HitLeftTileEvent = 1;
        public const int HitRightTileEvent = 2;
        public const int HitTopTileEvent = 3;
        public const int HitBottomTileEvent = 4;

        /// <summary>
        /// Returns the active crate
        /// </summary>
        public Crate Crate
        {
            get
            {
                return m_crate;
            }
        }

        public List<CrateMessage> Messages
        {
            get
            {
                return m_crateMessages;
            }
        }

        int m_width;
        /// <summary>
        /// World width (in world units)
        /// </summary>
        public int Width
        {
            get
            {
                return m_width;
            }
        }

        int m_height;
        /// <summary>
        /// World height (in world units)
        /// </summary>
        public int Height
        {
            get
            {
                return m_height;
            }
        }

        public Level Level
        {
            get;
            private set;
        }

        /// <summary>
        /// Returns whether all players are still alive (or not!)
        /// </summary>
        public bool IsGameOver
        {
            get
            {
                int startedCount = 0;
                foreach (Player it in m_players)
                {
                    if (it.HasBeenStarted)
                        startedCount++;
                    if (it.HasBeenStarted && it.IsAlive)
                        return false;
                }
                // one or more players started playing but are no longer with us!
                return startedCount==0?false:true;
            }
        }

        /// <summary>
        /// World gravity
        /// </summary>
        /// <remarks>
        /// Positive Y is "down"
        /// </remarks>
        public static readonly Vector2 Gravity = new Vector2(0, 9.8f);

        /// <summary>
        /// Scale to convert a unit in world space to pixel space
        /// </summary>
        public const int UnitToPixel = 32;

        /// <summary>
        /// Creates a new world with the given width and height
        /// </summary>
        /// <param name="game">Game instance</param>
        /// <param name="w">Width (in world unit)</param>
        /// <param name="h">Height (in world unit)</param>
        public World(Game game, TileMap map):
            base(game)
        {
            for (int i = 0; i < m_playerIdleTimers.Length; i++)
                m_playerIdleTimers[i] = new Timer(Configuration.IdlePlayerTimer, false);

            m_crate = new Crate();
            this.Map = map;
            this.Level = new Level(map);
            m_pathFinder = new PathFinder(this.Level);
            m_crate.Tile = Level.GetCrateSpawn();
            m_width = Level.Width;
            m_height = Level.Height;
        }

        public override void Initialize()
        {
            m_smokePS = GameServices.GetService<ParticleSystem>("Particles/SmallSmoke");
            m_teleportPS = GameServices.GetService<ParticleSystem>("Particles/Teleport");
            m_audio = GameServices.GetService<AudioManager>();
            base.Initialize();
        }

        public TileMap Map;

        public void LoadContent()
        {
            // do we already have a player??
            m_players[0] = new Player(this.Game, PlayerIndex.One);
            m_players[1] = new Player(this.Game, PlayerIndex.Two);

            // other systems
            RegisterWeapon(new PistolWeaponSystem(Game));               // 0
            RegisterWeapon(new GunWeaponSystem(Game));                  // 1
            RegisterWeapon(new ShotgunWeaponSystem(Game));              // 2
            RegisterWeapon(new MachineGunWeaponSystem(Game));           // 3
            RegisterWeapon(new MiniGunWeaponSystem(Game));              // 4
            RegisterWeapon(new RPGWeaponSystem(Game));                  // 5
            RegisterWeapon(new DiscGunWeaponSystem(Game));              // 6
            RegisterWeapon(new GrenadeLauncherWeaponSystem(Game));      // 7
            RegisterWeapon(new LandMineWeaponSystem(Game));             // 8
            RegisterWeapon(new BFGWeaponSystem(Game));                  // 9     
            RegisterWeapon(new TeleportWeaponSystem(Game));             // 10     
            RegisterWeapon(new FlameThrowerWeaponSystem(Game));         // 11     
            RegisterWeapon(new TheMaskWeaponSystem(Game));              // 12
            // RegisterWeapon(new LightSaberWeaponSystem(Game));              // 13
        }

        void RegisterWeapon(GameComponent weapon)
        {
            weapon.Initialize();
            m_weapons.Add((IWeapon)weapon);
            // register as a service
            GameServices.AddService(((IWeapon)weapon).Settings.Name, weapon);
        }

        void RegisterWeapon(IWeaponSystem weaponSystem)
        {
            weaponSystem.Initialize();
            m_weapons.Add((IWeapon)weaponSystem);
            // register as a service
            GameServices.AddService(((IWeapon)weaponSystem).Settings.Name, weaponSystem);
            //
            m_weaponSystems.Add(weaponSystem);
        }

        /// <summary>
        /// Returns the set of players
        /// </summary>
        /// <remarks>
        /// Entries in the table can be empty.
        /// </remarks>
        public Player[] Players
        {
            get
            {
                return m_players;
            }
        }

        /// <summary>
        /// Returns whether a player is active (e.g. started and alive)
        /// </summary>
        /// <param name="index">Player index</param>
        /// <returns>True if the player is active</returns>
        public bool IsActive(PlayerIndex index)
        {
            return m_players[(int)index].IsActive;
        }

        public bool AllPlayersDead
        {
            get
            {
                foreach (Player it in m_players)
                    if (it.IsAlive == true)
                        return false;
                return true;
            }
        }
        /// <summary>
        /// Returns whether the given player can react to user inputs
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public bool AcceptInput(PlayerIndex index)
        {
            return m_players[(int)index].IsAlive;
        }

        /// <summary>
        /// Create a new player
        /// </summary>
        /// <param name="index"></param>
        public void AddPlayer(PlayerIndex index)
        {
            Player p =  m_players[(int)index];           

            // restart ?
            if ( p.IsActive == false )
            {
                p.HasBeenStarted = true;
                p.Moveable.Reset();

                Vector2i pos = Level.GetPlayerSpawnPoint(index); 
                p.Moveable.Position = new Vector2(( pos.X + 0.5f ) * World.UnitToPixel, (pos.Y + 0.5f) * World.UnitToPixel);
                // default weapon (pistol) - assumed to be first in the set
                p.Weapon = m_weapons[5];
                p.Moveable.TileX = WorldToMapUnit(p.Position.X);
                p.Moveable.TileY = WorldToMapUnit(p.Position.Y);
                p.IsAlive = true;
                p.IsActive = true;
                p.Start();
            }
        }

        void SpawnBadGuy(bool isSuperBadGuy, SpawnPattern.Direction dir)
        {
            // don't spawn a NPC if all players are dead
            if (AllPlayersDead)
                return;

            // wait for some bad guy to die before creating new one!!!
            if (m_npcActors.Count < 32)
            {
                BadGuy bg = new BadGuy(m_nextBadGuyID++, this.Game, isSuperBadGuy);
                bg.IsRunningRight = (dir == SpawnPattern.Direction.Right);
                bg.Moveable.Position = new Vector2(12 * UnitToPixel, UnitToPixel / 2);
                bg.Moveable.TileX = WorldToMapUnit(bg.Moveable.Position.X);
                bg.Moveable.TileY = WorldToMapUnit(bg.Moveable.Position.Y);

                m_npcActors.Add(bg);
            }
        }

        void SpawnFlyingBadGuy(PlayerIndex index)
        {
            // don't spawn a NPC if all players are dead
            if (AllPlayersDead)
                return;

            // wait for some bad guy to die before creating new one!!!
            if (m_npcActors.Count < 32)
            {
                int flyingCount = 0;
                foreach (NPCBase it in m_npcActors)
                {
                    if (it.NPCType == 2)
                    {
                        flyingCount++;
                        if (flyingCount > 4)
                            return;
                    }
                }

                NPCBase bg = new FlyingBadGuy(m_nextBadGuyID++, this.Game, index);
                bg.Moveable.Position = new Vector2(13 * UnitToPixel, 0);
                bg.Moveable.TileX = WorldToMapUnit(bg.Moveable.Position.X);
                bg.Moveable.TileY = WorldToMapUnit(bg.Moveable.Position.Y);

                m_npcActors.Add(bg);

                m_audio.PlayCue("FlyingBadGuy/Spawn");
            }
        }

        SpawnPattern CurrentPattern
        {
            get
            {
                return m_patterns[m_currentPattern];
            }
        }

        void PickNextSpawnPattern()
        {
            m_currentPattern = g_rnd.Next(m_patterns.Length);
            m_patterns[m_currentPattern].Start();
        }

        bool m_checkPlayerOneFirst = true;
        public override void Update(GameTime gameTime)
        {
            m_shakeScreenTimer.Update(gameTime);
            if (m_shakeScreenTimer.IsSignaled)
            {
                // make sure the screen gets back to normal!
                m_shakeForce = Vector2.Zero;
            }
            else if (m_shakeScreenTimer.IsActive)
            {
                m_shakeForce = Vector2.SmoothStep(m_shakingOffset, Vector2.Zero, m_shakeScreenTimer.NormalizedAge);
            }


            CurrentPattern.Update(gameTime);

            if (!CurrentPattern.IsActive)
                PickNextSpawnPattern();

            // create a bad guy
            if (CurrentPattern.Spawn)
                SpawnBadGuy(false, CurrentPattern.SpawnDirection);

            for(int i=0;i<m_playerIdleTimers.Length;i++)
            {
                m_playerIdleTimers[i].Update(gameTime);
                if (m_playerIdleTimers[i].IsSignaled)
                {
                    GameServices.EventDispatcher.PostEvent(this.ID, FlyingBadGuy.SpawnFlyingBadGuyEvent, new SpawnFlyingBadGuyEventArgs((PlayerIndex)i));
                }
            }

            //
            for(int i=0;i<MaxPlayers;i++)
            {
                Player it = m_players[i];
                // waiting for the player...
                if (!it.IsActive)
                    continue;               

                // record player position
                int tilex = -1;
                if (it.IsAlive)
                {
                    tilex = it.Moveable.TileX;
                }

                it.Update(gameTime);

                if (it.IsAlive)
                {
                    Adjust(it.Moveable, gameTime);

                    // is the player moving??
                    if (tilex == it.Moveable.TileX)
                    {
                        m_playerIdleTimers[i].Start();
                    }
                    else
                    {
                        m_playerIdleTimers[i].Stop();
                    }
                }
            }

            //
            for(int i=m_npcActors.Count-1;i>=0;i--)
            {
                NPCBase bd = m_npcActors[i];

                // no more active?
                if (!bd.IsActive)
                {
                    m_npcActors.RemoveAt(i);
                    continue;
                }

                bd.Update(gameTime);

                // no need to "adjust" dead objects!
                if (bd.IsAlive)
                {
                    Adjust(bd.Moveable, gameTime);
                    // compute collision with players
                    Intersect(bd);
                }
            }

            // ensure crate gets checked against player in "random" order
            // avoids the feeling of player 1 having the crate more often
            if (!Intersect(gameTime, m_checkPlayerOneFirst ? PlayerIndex.One : PlayerIndex.Two, m_crate))
                Intersect(gameTime, m_checkPlayerOneFirst ? PlayerIndex.Two : PlayerIndex.One, m_crate);

            m_checkPlayerOneFirst = !m_checkPlayerOneFirst;

            // update crate messages
            for (int i = m_crateMessages.Count - 1; i >= 0; i--)
            {
                CrateMessage msg = m_crateMessages[i];
                msg.Update(gameTime);
                if (!msg.IsActive)
                    m_crateMessages.RemoveAt(i);
            }

            // other sub-systems
            foreach(IWeaponSystem it in m_weaponSystems)
                it.Update(gameTime);
        }

        IWeapon PickNextWeapon(Player p)
        {
            // pick a new weapon (except the pistol)
            int weaponId = g_rnd.Next(1, m_weapons.Count);
            IWeapon nextWeapon = m_weapons[weaponId];
            if (nextWeapon.Settings.Name == p.Weapon.Settings.Name)
            {
                // pick the next weapon, but not the pistol and not beyond array size!!
                weaponId = Math.Max(2,(weaponId + 1) % m_weapons.Count);
                nextWeapon = m_weapons[weaponId];
            }
            return nextWeapon;
        }

        bool Intersect(GameTime gameTime, PlayerIndex index, Crate crate)
        {
            Player p = m_players[(int)index];
            if (p.IsActive && p.IsAlive)
            {
                if (crate.Intersects(p.Moveable))
                {
                    m_crateScore++;

                    // sound feedback
                    m_audio.PlayCue("WeaponChange");

                    IWeapon nextWeapon = PickNextWeapon(p);
                    // add message
                    m_crateMessages.Add(new CrateMessage(gameTime, new Vector2((crate.Tile.X + 0.5f) * UnitToPixel, (crate.Tile.Y + 0.5f) * UnitToPixel),nextWeapon.Settings.Name));

                    // smoke
                    for (int i = 0; i < 4; i++)
                    {
                        Vector3 smokePos = new Vector3(
                            MathHelper.Lerp(crate.Tile.X * UnitToPixel, (crate.Tile.X + 1) * UnitToPixel, (float)g_rnd.NextDouble()),
                            MathHelper.Lerp(crate.Tile.Y * UnitToPixel, (crate.Tile.Y + 1) * UnitToPixel, (float)g_rnd.NextDouble()),0);
                        m_smokePS.AddParticle(smokePos, Vector3.Zero);
                    }

                    // relocate crate
                    crate.Tile = Level.GetCrateSpawn();

                    // notify player he got a new weapon!!
                    GameServices.EventDispatcher.PostEvent(p.ID, Crate.HitEvent, new CrateHitEventArgs(nextWeapon));

                    return true;
                }
            }
            return false;
        }

        public void Intersect(NPCBase bd)
        {
            //
            foreach (Player it in m_players)
            {
                if (it != null && it.IsAlive && bd.Moveable.Intersects(it.Moveable))
                {
                    // check against *all* players
                    GameServices.EventDispatcher.PostEvent(it.ID, NPCBase.DieEvent, null);            
                }
            }
        }

        public void Move(PlayerIndex index, Vector2 direction)
        {
            Player p = m_players[(int)index];
            p.Move(direction);
        }

        public void Jump(PlayerIndex index)
        {
            Player p = m_players[(int)index];
            p.Jump();
        }

        public void Fire(GameTime gameTime, PlayerIndex index)
        {
            Player p = m_players[(int)index];
            p.Fire(gameTime);
        }

        /// <summary>
        /// Indicates whether given rectangle is within World boundaries.
        /// </summary>
        /// <param name="rect">Rectangle to test against.</param>
        /// <returns>true if the rectangle is within bounds, false otherwise.</returns>
        public bool IsOutside(Rectangle rect)
        {
            return (new Rectangle(0, 0, Width * UnitToPixel, Height * UnitToPixel)).Intersects(rect)==false;
        }

        bool IsTouchingGround(Moveable p)
        {
            return IsTouchingGround(p.Position, p.Width, p.Height);
        }

        bool IsTouchingGround(Vector2 position, int width, int height)
        {
            int bottom = (int)Math.Floor((position.Y + height / 2 + 1) / World.UnitToPixel);
            int left = (int)Math.Floor((position.X - width / 2) / World.UnitToPixel);
            int right = (int)Math.Floor((position.X + width / 2 - 1) / World.UnitToPixel);

            bool leftHit = Level.IsNotWalkable(left, bottom);
            // no need to go further?
            if (leftHit)
                return true;
            bool rightHit = Level.IsNotWalkable(right, bottom);
            if (rightHit)
                return true;

            // no hit
            return false;
        }

        bool IsWalkable(TileHitTest.TouchingSide dir, Moveable p, float x, float y)
        {
            return IsWalkable(dir, p.Height, p.Width, x, y);
        }

        bool IsWalkable(TileHitTest.TouchingSide dir, int height, int width, float x, float y)
        {
            int top = (int)Math.Floor((y - height / 2) / World.UnitToPixel);
            int bottom = (int)Math.Floor((y + height / 2 - 1) / World.UnitToPixel);
            int left = (int)Math.Floor((x - width / 2) / World.UnitToPixel);
            int right = (int)Math.Floor((x + width / 2 - 1) / World.UnitToPixel);
            switch (dir)
            {
                case TileHitTest.TouchingSide.Top:
                    return Level.IsWalkable(left, top) && Level.IsWalkable(right, top);
                case TileHitTest.TouchingSide.Bottom:
                    return Level.IsWalkable(left, bottom) && Level.IsWalkable(right, bottom);
                case TileHitTest.TouchingSide.Right:
                    return Level.IsWalkable(right, top) && Level.IsWalkable(right, bottom);
                case TileHitTest.TouchingSide.Left:
                    return Level.IsWalkable(left, top) && Level.IsWalkable(left, bottom);
            }
            throw new InvalidOperationException("Unknown hit test direction");
        }

        public void Adjust(Moveable p, GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

            // is the object on a deadly tile?
            if (Level.IsDeadly(p.TileX, p.TileY))
            {
                GameServices.EventDispatcher.PostEvent(p.OwnerID, NPCBase.DieEvent, null);
                // no need to go further!!
                return;
            }

            // 
            p.UpdateVelocity(dt);

            p.TouchingGround = false;
            p.Drag = 0;

            if (p.Velocity.Y < 0)
            {
                if (!IsWalkable(TileHitTest.TouchingSide.Top, p, p.Position.X, p.Position.Y + dt * p.Velocity.Y))
                {
                    // stay put!
                    p.Position.Y = p.TileY * World.UnitToPixel + p.Height / 2;
                    p.HitCeiling();                    
                    // notify component
                    GameServices.EventDispatcher.PostEvent(p.OwnerID, World.HitTopTileEvent, null);
                }
            }
            else if (p.Velocity.Y > 0)
            {
                if (!IsWalkable(TileHitTest.TouchingSide.Bottom, p, p.Position.X, p.Position.Y + dt * p.Velocity.Y))
                {
                    p.Position.Y = (p.TileY + 1) * World.UnitToPixel - p.Height / 2;
                    p.HitFloor();
                    // notify component
                    GameServices.EventDispatcher.PostEvent(p.OwnerID, World.HitBottomTileEvent, null);
                }
            }
            
            if (p.Velocity.X < 0)
            {
                if (!IsWalkable(TileHitTest.TouchingSide.Left, p, p.Position.X + dt * p.Velocity.X, p.Position.Y))
                {
                    p.Position.X = p.TileX * World.UnitToPixel + p.Width / 2;
                    p.HitWall();
                    // notify component
                    GameServices.EventDispatcher.PostEvent(p.OwnerID, World.HitLeftTileEvent, null);
                }
            }
            else if (p.Velocity.X > 0)
            {
                if (!IsWalkable(TileHitTest.TouchingSide.Right, p, p.Position.X + dt * p.Velocity.X, p.Position.Y))
                {
                    p.Position.X = (p.TileX + 1) * World.UnitToPixel - p.Width / 2;
                    p.HitWall();
                    // notify component
                    GameServices.EventDispatcher.PostEvent(p.OwnerID, World.HitRightTileEvent, null);
                }
            }

            // commit position
            p.UpdatePosition(dt);

            // commit ground position
            p.TouchingGround = IsTouchingGround(p);
        }

        public void AdjustPoint(ref Vector2 position, ref Vector2 velocity, ref bool isTouchingGround, float mass, float wallDrag, GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

            int tilex = WorldToMapUnit(position.X);
            int tiley = WorldToMapUnit(position.Y);

            // 
            if (!isTouchingGround)
            {
                velocity.X += dt * mass * World.Gravity.X;
                velocity.Y += dt * mass * World.Gravity.Y;
            }
            else
            {
                velocity.X -= dt * wallDrag * velocity.X;
            }
            System.Diagnostics.Debug.Assert(Math.Abs(dt * velocity.X) < World.UnitToPixel);
            System.Diagnostics.Debug.Assert(Math.Abs(dt * velocity.Y) < World.UnitToPixel);

            if (velocity.Y < 0)
            {
                if (!IsWalkable(TileHitTest.TouchingSide.Top, 2, 2, position.X, position.Y + dt * velocity.Y))
                {
                    // stay put!
                    position.Y = tiley * World.UnitToPixel + 1;
                    velocity.Y = 0;
                }
            }
            else if (velocity.Y > 0)
            {
                if (!IsWalkable(TileHitTest.TouchingSide.Bottom, 2, 2, position.X, position.Y + dt * velocity.Y))
                {
                    position.Y = (tiley + 1) * World.UnitToPixel - 1;
                    velocity.Y = 0;                                       
                }
            }
            
            if (velocity.X < 0)
            {
                if (!IsWalkable(TileHitTest.TouchingSide.Left, 2, 2, position.X + dt * velocity.X, position.Y))
                {
                    position.X = tilex * World.UnitToPixel + 1;
                    velocity.X = 0;
                }
            }
            else if (velocity.X > 0)
            {
                if (!IsWalkable(TileHitTest.TouchingSide.Right, 2, 2, position.X + dt * velocity.X, position.Y))
                {
                    position.X = (tilex + 1) * World.UnitToPixel - 1;
                    velocity.X = 0;
                }
            }

            // commit position
            position.X += dt * velocity.X;
            position.Y += dt * velocity.Y;

            // commit ground position
            isTouchingGround = IsTouchingGround(position, 2, 2);
        }

        /// <summary>
        /// Restart the game.
        /// </summary>
        public void Start()
        {
            GameServices.EventDispatcher.RegisterClient(this);
            // pick a pattern
            PickNextSpawnPattern();

            foreach (Player p in m_players)
                p.Start();
        }

        /// <summary>
        /// Stops the game.
        /// </summary>
        public void Stop()
        {
            // stop bumping up the world!!
            m_shakeForce = Vector2.Zero;
            m_shakeScreenTimer.Stop();

            for (int i = 0; i < m_playerIdleTimers.Length; i++)
                m_playerIdleTimers[i].Stop();

            m_npcActors.Clear();
            m_crateMessages.Clear();
            foreach(IWeaponSystem it in m_weaponSystems)
                it.Clear();
            foreach (Player p in m_players)
                p.Stop();
            m_crateScore = 0;
            GameServices.EventDispatcher.Clear();
        }

        public Intersection? Intersect(Vector3 position, Vector3 direction, float length, float radius)
        {
            // fake intersection
            if (position.X < 0 ||
                position.Y < 0 ||
                position.X > Width * UnitToPixel ||
                position.Y > Height * UnitToPixel)
                return new Intersection() 
                    { 
                        IsTile = true, //fake but simpler!
                        Position = new Vector2(MathHelper.Clamp(position.X,0, Width * UnitToPixel),MathHelper.Clamp(position.Y,0, Height * UnitToPixel)) 
                    };

            // try actors
            Capsule2d capsule = new Capsule2d(
                new Ray(new Vector3(position.X, position.Y, 0), new Vector3(direction.X, direction.Y, 0)),
                length, 
                radius);
            // for now: single hit only
            foreach (BadGuy it in m_npcActors)
            {
                // make sure to not collide with inactive entities!
                if (it.IsAlive && it.Moveable.Intersects(capsule))
                {
                    return new Intersection() { TargetID = it.ID };
                }
            }

            // then floor
            int x = WorldToMapUnit(position.X);
            int y = WorldToMapUnit(position.Y);

            if (Level.IsNotWalkable(x, y))
                return new Intersection() { IsTile = true };

            // no intersections!
            return null;
        }

        public int ID
        {
            get 
            {
                return 500;
            }
        }

        public bool ProcessEvent(GameTime gameTime, int msgId, EventArgs e)
        {
            switch (msgId)
            { 
                case BadGuy.SpawnSuperBadGuyEvent:
                    SpawnBadGuy(true, (g_rnd.Next(2)==1)?SpawnPattern.Direction.Right:SpawnPattern.Direction.Left);
                    break;

                case FlyingBadGuy.SpawnFlyingBadGuyEvent:
                    SpawnFlyingBadGuy(((SpawnFlyingBadGuyEventArgs)e).Index);
                    break;

                case TeleportWeaponSystem.TeleportEventID:
                    TeleportPlayer(((TeleportEventArgs)e).PlayerID);
                    break;
            }
            return true;
        }

        void TeleportPlayer(int playerId)
        {
            // grab player
            Player p = m_players[(int)playerId];
            // is player still alive?
            if (p.IsAlive)
            {
                for (int i = 0; i < p.Moveable.Width; i++)
                    m_teleportPS.AddParticle(new Vector3(p.Moveable.X - p.Moveable.Width / 2 + i, p.Moveable.Y + p.Moveable.Height / 2, 0), new Vector3(0,-1,0));

                // 
                p.Moveable.Velocity = Vector2.Zero;
                p.Moveable.Teleport(new Vector2(Crate.Tile.X * World.UnitToPixel + World.UnitToPixel / 2, Crate.Tile.Y * World.UnitToPixel - World.UnitToPixel / 2));

                for (int i = 0; i < p.Moveable.Width; i++)
                    m_teleportPS.AddParticle(new Vector3(p.Moveable.X - p.Moveable.Width / 2 + i, p.Moveable.Y - p.Moveable.Height / 2, 0), new Vector3(0, 1, 0));
            }
        }

        /// <summary>
        /// Converts a floating point position in world coordinates to map (tile) units
        /// </summary>
        /// <param name="x">Coordinate</param>
        /// <returns>Tile index</returns>
        public int WorldToMapUnit(float x)
        {
            return (int)Math.Floor(x / World.UnitToPixel);
        }

        /// <summary>
        /// Converts a floating point position in world coordinates to map (tile) units
        /// </summary>
        /// <param name="pos">Coordinates</param>
        /// <returns>Tile index</returns>
        public Vector2i WorldToMapUnit(Vector2 pos)
        {
            return new Vector2i(WorldToMapUnit(pos.X), WorldToMapUnit(pos.Y));
        }

        /// <summary>
        /// Converts tile coordinates to world position.
        /// </summary>
        /// <param name="tile">Tile</param>
        /// <returns>World coordinates</returns>
        public Vector2 MapToWorldUnit(Vector2i tile)
        {
            return new Vector2(tile.X * World.UnitToPixel, tile.Y * World.UnitToPixel);
        }

        /// <summary>
        /// Converts tile coordinates to world position, centered on the tile.
        /// </summary>
        /// <param name="tile">Tile</param>
        /// <returns>World coordinates</returns>
        public Vector2 MapCenterToWorldUnit(Vector2i tile)
        {
            return new Vector2((tile.X + 0.5f) * World.UnitToPixel, (tile.Y + 0.5f) * World.UnitToPixel);
        }

        /// <summary>
        /// Returns the 2D path between 2 points (in world coordinates).
        /// </summary>
        /// <param name="startPoint"></param>
        /// <param name="endPoint">target point</param>
        /// <returns>A path in <b>map</b> coordinates.</returns>
        public List<Vector2i> GetPath(Vector2 startPoint, Vector2 endPoint)
        {
            return m_pathFinder.GetPath(
                WorldToMapUnit(startPoint),
                WorldToMapUnit(endPoint));
        }

        /// <summary>
        /// Returns the 2D path between 2 points (in world coordinates).
        /// </summary>
        /// <param name="m">A <see cref="Moveable"/> instance.</param>
        /// <param name="endPoint">target point</param>
        /// <returns>A Path in <b>map</b> coordinates.</returns>
        public List<Vector2i> GetPath(Moveable m, Vector2 endPoint)
        {
            return m_pathFinder.GetPath(
                new Vector2i(m.TileX,m.TileY),
                WorldToMapUnit(endPoint));
        }

        public bool IsSingleShot(PlayerIndex index)
        {
            Player p = m_players[(int)index];
            return p.Weapon.Settings.ContinousFire == false;
        }

        /// <summary>
        /// Returns how much the world is moving (e.g. shaking)
        /// </summary>
        public Vector2 ShakeForce
        {
            get
            {
                return m_shakeForce;
            }
        }

        /// <summary>
        /// Let's shake baby
        /// </summary>
        /// <remarks>
        /// Moves the screen by a random factor
        /// </remarks>
        public void Shake()
        {
            // note: should be a function of the actual screen size (but what heuristic - a tile in screen space??)
            m_shakingOffset.X = g_rnd.Next(-10, 10);
            m_shakingOffset.Y = g_rnd.Next(-10, 10);
            m_shakeScreenTimer.Start();
        }
    }
}
